﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Autofac;

namespace Zinnia.Common.IOC
{
    public static class AutofacIocHelper
    {
        /// <summary>
        /// 将服务注入target的属性
        /// </summary>
        /// <param name="scope"></param>
        /// <param name="target"></param>
        public static void Setup(this ILifetimeScope scope, object target)
        {
            var targetType = target.GetType();
            InjectUnsetProperties(scope, target, targetType);
        }

        /// <summary>
        /// 原生方式注入
        /// </summary>
        /// <param name="scope"></param>
        /// <param name="target"></param>
        /// <param name="targetType"></param>
        public static void InjectUnsetProperties(ILifetimeScope scope, object target, Type targetType)
        {
            Func<object, object> action = scope.InjectUnsetProperties;
            var method = action.Method.GetGenericMethodDefinition().MakeGenericMethod(targetType);
            method.Invoke(null, new[] { scope, target, });
        }

        /// <summary>
        /// 使用反射方式注入
        /// </summary>
        /// <param name="scope"></param>
        /// <param name="target"></param>
        /// <param name="targetType"></param>
        public static void ReflectionSetup(ILifetimeScope scope, object target, Type targetType)
        {
            var props = Props(targetType);
            foreach (var prop in props)
            {
                var value = scope.Resolve(prop.PropertyType);
                prop.SetValue(target, value, null);
            }
        }

        /// <summary>
        /// 动态变异缓存
        /// </summary>
        private static IDictionary<Type, Action<ILifetimeScope, object>> Setups = new Dictionary<Type, Action<ILifetimeScope, object>>();

        /// <summary>
        /// 将类型对应的注入逻辑动态编译成字节码
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static Action<ILifetimeScope, object> CompileSetup(Type type)
        {
            Action<ILifetimeScope, object> action;
            if (Setups.TryGetValue(type, out action))
            {
                return action;
            }
            var props = Props(type);
            var scope = Expression.Parameter(typeof(ILifetimeScope), "scope");
            var targetObj = Expression.Parameter(typeof(object), "targetObj");
            var target = Expression.Variable(type, "target");
            var set = Expression.Assign(target, Expression.Convert(targetObj, type));
            var sets = props.Select(e =>
            {
                var left = Expression.Property(target, e);
                Func<IComponentContext, object> func = ResolutionExtensions.Resolve<object>;
                var method = func.Method.GetGenericMethodDefinition().MakeGenericMethod(e.PropertyType);
                var right = Expression.Call(method, scope);
                return Expression.Assign(left, right);
            }).ToList();
            var setsets = new[] { set }.Union(sets).ToList();
            var body = Expression.Block(variables: new[] { target, }, expressions: setsets);
            var lambda = Expression.Lambda<Action<ILifetimeScope, object>>(body, scope, targetObj);
            action = lambda.Compile();
            Setups[type] = action;
            return action;
        }

        /// <summary>
        /// 类型对于的需要注入的属性缓存
        /// </summary>
        private static IDictionary<Type, PropertyInfo[]> CacheProps = new Dictionary<Type, PropertyInfo[]>();

        /// <summary>
        /// 获取需要注入的属性
        /// </summary>
        public static PropertyInfo[] Props(Type type)
        {
            PropertyInfo[] props;
            if (CacheProps.TryGetValue(type, out props))
            {
                props = props.ToArray();
                return props;
            }

            props = type.
                GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            props = props.Where(e =>
                !e.PropertyType.IsValueType &&
                Type.GetTypeCode(e.PropertyType) == TypeCode.Object &&
                e.PropertyType.IsInterface &&
                !(e.PropertyType.Namespace ?? "").StartsWith("System.")
            ).ToArray();

            CacheProps[type] = props;
            return props.ToArray();
        }
    }
}
